package sg.edu.nus.iss.vmcs.customer;

/*
 * Copyright 2003 ISS.
 * The contents contained in this document may not be reproduced in any
 * form or by any means, without the written permission of ISS, other
 * than for the purpose for which it has been supplied.
 *
 */

import sg.edu.nus.iss.vmcs.store.ProductBrand;
import sg.edu.nus.iss.vmcs.store.ProductStore;
import sg.edu.nus.iss.vmcs.store.ProductStoreItem;
import sg.edu.nus.iss.vmcs.store.Store;
import sg.edu.nus.iss.vmcs.system.Environment;
import sg.edu.nus.iss.vmcs.system.MainController;
import sg.edu.nus.iss.vmcs.system.SimulatorControlPanel;
import sg.edu.nus.iss.vmcs.customer.*;

/**
 * This control object coordinates the customer transactions for selection of a
 * product brand, coin input, storage of coins and termination requests for
 * ongoing transactions.
 * 
 * @version 1.0 11 Oct 2008
 * @author Toh Kim Huat
 * 
 * Updated with CoinInputBoxAdapter Implementation
 * @version 1.1 2 Jan 2010
 * @author Justin
 *
 * Updated with CoinInputBoxAdapter Implementation
 * @version 1.1 2 Jan 2010
 * @author Ravishankar Rajappa
 * 
 * Updated TerminateFault method
 * @version 1.1 2 Jan 2010
 * @author Hema chandran

 */
public class TransactionController {

	public static final int FAULT_TYPE_CHANGE = 1;
	public static final int FAULT_TYPE_STORE_COIN = 2;
	public static final int FAULT_TYPE_DISPENSE = 3;
	
	/**
	 * Boolean set to TRUE when change is successfully issued during the
	 * transaction
	 */
	private boolean changeGiven;

	/**
	 * Boolean set to TRUE when the product is successfully dispensed during the
	 * transaction
	 */
	private boolean productDispensed;

	/**
	 * Price of the selected product
	 */
	private int price;

	/**
	 * Identifier of the selected product
	 */
	private int selection;

	/**
	 * Main controller which created this controller
	 */
	private MainController mainController;

	/**
	 * Customer panel
	 */
	private CustomerPanel customerPanel;
	
	/**
	 * Dispense controller
	 */
	private DispenseController dispenseController;
	
	public void setDispenseController(DispenseController dispenseController) {
		this.dispenseController = dispenseController;
	}

	/**
	 * Change giver
	 */
	private ChangeGiver changeGiver;
	
	private TerminateFaultStrategy strategy;
	
	/**
	 * Coin receiver
	 */
	private CoinReceiver coinReceiver;
		
	public void setCoinReceiver(CoinReceiver coinReceiver) {
		this.coinReceiver = coinReceiver;
	}

	/**
	 * @author Ravishankar Rajappa
	 * Coin Input Box Adapter
	 */
	private CoinInputBoxTarget coinInputBoxTarget;
	
	/**
	 * @author justin
	 * Coin Input Box Adapter
	 * Modified Ravishankar Rajappa - Not required with abstract factory.
	 */
	//private CoinInputBoxAdapter coinInputBoxAdapter;
	
	/**
	 * @author Ravishankar Rajappa
	 * Abstract Factory
	 */
	private AbstractFactoryInterface factoryInterface;
	
	/**
	 * Default constructor
	 * 
	 * @param mainController Main controller which created this controller
	 * Modified - Ravishankar Rajappa
	 * Added create adapters method
	 */
	public TransactionController(MainController mainController) {
		this.mainController = mainController;
		this.dispenseController = new DispenseController(this);
		this.changeGiver = new ChangeGiver(this);
		this.coinReceiver = new CoinReceiver(this);		
		CreateAdapters();
	}

	
	/**
	 * @author Ravishankar Rajappa
	 * This operation will create the adapters required, 
	 * based on the value in the property file
	 */
	private void CreateAdapters(){
		if(Environment.getAdapter().equalsIgnoreCase("AWT")) {
			this.factoryInterface = new AWTFactory();
		}
		if(Environment.getAdapter().equalsIgnoreCase("HW")) {
			this.factoryInterface = new HWFactory();
		}
		this.coinInputBoxTarget = factoryInterface.getCoinInputBox(coinReceiver);
	}
	
	
	/**
	 * This operation will cancel an ongoing customer transaction
	 */
	public void cancelTransaction() {
		this.coinReceiver.stopReceive();
		this.coinReceiver.refundCash();
		this.dispenseController.allowSelection(true);
	}
	
	/**
	 * This operation will close down the transaction control function of the
	 * vending machine
	 */
	public void closeDown() {
		this.customerPanel.closeDown();
	}

	/**
	 * This operation is performed when the Transaction Controller is informed
	 * that coin entry is complete and the money received is sufficient to
	 * dispense the product. The following actions are performed. 
	 * 
	 * 1. Display the product. 
	 * 2. Give change if necessary. 
	 * 3. Store the Coins that have been entered into the Cash Store. 
	 * 4. Reset the Product Selection Box to allow further transactions.
	 */
	public void completeTransaction() {
		// dispense the selected product
		this.dispenseController.dispenseProduct(this.selection);
		
		// calculate amount of change and give change
		int totalInserted = this.coinReceiver.getTotalInserted();
		int change = totalInserted - price;
		if (change > 0) {
			this.changeGiver.giveChange(change);
		}
		
		boolean cashStored = this.coinReceiver.storeCash();
		if (cashStored) {
			this.dispenseController.allowSelection(true);
		}
		else {
			terminateTransaction();
		}
	}
	
	/**
	 * Display the customer panel
	 */
	public void displayCustomerPanel() {
		this.customerPanel = new CustomerPanel(mainController
				.getSimulatorControlPanel(), this);
		this.customerPanel.display();
		this.dispenseController.updateProductPanel();
		this.dispenseController.allowSelection(true);
		this.changeGiver.displayChangeStatus();
		this.coinInputBoxTarget.setCoinInputBox(customerPanel.getCoinInputBox());
		this.coinReceiver.setActive(false);
	}
	
	/**
	 * This operation processes the money received by the Coin Receiver during
	 * the progress of a transaction. The following actions are performed during
	 * this operation:
	 * 
	 * 1. The current total money inserted is obtained from the Coin Receiver.
	 * 2. If the received money is more than or equal to the price of the product,
	 * operation completeTransaction of the Transaction Controller is triggered.
	 * 3. If the received money is less than the price of the product, the Coin
	 * Receiver is instructed to continue receiving the coins.
	 * 
	 * @param total
	 *            Total number of money received
	 */
	public void processMoneyReceived(int total) {
		if (total >= this.price) {
			completeTransaction();
		}
		else {
			this.coinReceiver.continueReceive();
		}
	}
	
	/**
	 * This operation refreshes the Customer Panel when the maintainer logs-out
	 */
	public void refreshCustomerPanel() {
		this.mainController.getSimulatorControlPanel().setActive(
				SimulatorControlPanel.ACT_CUSTOMER, true);
		this.dispenseController.updateProductPanel();
		this.dispenseController.allowSelection(true);
		this.changeGiver.displayChangeStatus();
	}
	
	/**
	 * This operation will start the customer transaction. It receives the
	 * identification for the selected product brand (item) from the Customer
	 * Panel. The following actions are performed in the operation: 
	 * 
	 * 1. The price of the selected item is obtained. 
	 * 2. The Refund/Change Tray Display is reset. 
	 * 3. The Can Collection Box is reset. 
	 * 4. The Product Selection Box is deactivated to disallow the selection of 
	 * further products when the transaction is in progress. 
	 * 5. The Coin Receiver will be instructed to start receiving the coins
	 * 
	 * @param identifier
	 *            Identifier of the selected product brand
	 */
	public void startTransaction(int identifier) {
		// get the selected product brand
		ProductStore productStore = (ProductStore) this.mainController
				.getStoreController().getStore(Store.PRODUCT);
		ProductStoreItem productStoreItem = (ProductStoreItem) productStore
				.getStoreItem(identifier);
		ProductBrand productBrand = (ProductBrand) productStoreItem.getContent();
		this.selection = identifier;
		this.price = productBrand.getPrice();
		this.changeGiver.resetChange();
		this.dispenseController.resetCan();
		this.changeGiver.displayChangeStatus();
		this.dispenseController.allowSelection(false);
		this.coinReceiver.startReceive();

	}
	
	/**
	 * If the Transaction Controller is informed that a fault was discovered
	 * while dispensing a product, giving change or storing Coins, it will use
	 * this operation to deactivate the Product Selection Box and instruct the
	 * Coin Receiver to refund the money inserted by the customer.
	 */
	public void terminateFault(int faultType) {
//		switch (faultType) {
//		case FAULT_TYPE_CHANGE:
//			this.dispenseController.allowSelection(false);
//			this.coinReceiver.storeCash();
//			break;
//		case FAULT_TYPE_STORE_COIN:
//			this.dispenseController.allowSelection(false);
//			break;
//		case FAULT_TYPE_DISPENSE:
//			this.dispenseController.allowSelection(false);
//			this.coinReceiver.refundCash();
//			break;
//		}
		strategy = TerminateFaultStrategyFactory.create(faultType);
		strategy.terminateFault(this);
	}
	
	/**
	 * If the Transaction Controller receives a request to terminate the current
	 * transaction then following will occur: 
	 * 
	 * 1. If there is no transaction in progress or coin input is completed 
	 * then the Customer Panel will be instructed to deactivate the Product 
	 * Selection Box. 
	 * 2. If coin input is not yet complete, the Coin Receiver will be 
	 * instructed to stop coin input and refund the money entered so far. 
	 * 3. The Product Selection Box is then reset to allow further transactions.
	 */
	public void terminateTransaction() {
		this.dispenseController.allowSelection(false);
		if (this.coinReceiver.getTotalInserted() > 0) {
			this.coinReceiver.stopReceive();
			this.coinReceiver.refundCash();
		}
	}

	public MainController getMainController() {
		return mainController;
	}

	public CustomerPanel getCustomerPanel() {
		return customerPanel;
	}

	public boolean isChangeGiven() {
		return changeGiven;
	}

	public boolean isProductDispensed() {
		return productDispensed;
	}

	public int getPrice() {
		return price;
	}

	public int getSelection() {
		return selection;
	}

	public DispenseController getDispenseController() {
		return dispenseController;
	}

	public ChangeGiver getChangeGiver() {
		return changeGiver;
	}

	public CoinReceiver getCoinReceiver() {
		return coinReceiver;
	}

	public  CoinInputBoxTarget getCoinInputAdapter() {
		return coinInputBoxTarget;
	}


}
